#include "ioqueue.h"
#include "debug.h"
#include "interrupt.h"
#include "global.h"

/* 返回下一个位置 */
static int32_t _next_pos(int32_t pos){
    /* 取模保证不会越界 */
    return (pos + 1) % IO_QUEUE_BUF_SIZE;
}

uint8_t ioqueue_empty(struct ioqueue* ioq){
    /* 当头和尾相等则证明空 */
    return (ioq->head == ioq->tail) ? 1 : 0;
}

uint8_t ioqueue_full(struct ioqueue* ioq){
    /* 写入的下一个数据等于要读出的数据的位置则为满 */
    return (_next_pos(ioq->head) == ioq->tail) ? 1 : 0;
}

static void _ioq_wait(struct task_struct** t){
    /* 保证二级指针不为空且一级指针为空才可以 */
    ASSERT(t != NULL && *t == NULL);
    *t = running_task();
    thread_block(TASK_BLOCKED);
}

static void _ioq_wakeup(struct task_struct** t){
    ASSERT(t != NULL && *t != NULL);
    thread_unblock(*t);
    /* 解锁后要设置为空 */
    *t = NULL;
}

char ioqueue_get_char(struct ioqueue* ioq){
    char _ch = 0;

    ASSERT(intr_get_status() == INTR_OFF);

    while(ioqueue_empty(ioq)){
        /* 若多个线程同时访问空队列 则第二个线程之后的线程则会在这里阻塞 等待第一个线程 */
        lock_acquire(&ioq->lock);
        _ioq_wait(&ioq->consumer);
        lock_release(&ioq->lock);
    }
    /* 获取要读出的字节 */
    _ch = ioq->buf[ioq->tail];
    /* 指向下一个要读出的位置 */
    ioq->tail = _next_pos(ioq->tail);

    /* 如果读出了一个字节并且等待队列为空的生产者不为NULL,则唤醒 */
    if(ioq->producer != NULL){
        _ioq_wakeup(&ioq->producer);
    }

    return _ch;
}

void ioqueue_put_char(struct ioqueue* ioq, char ch){
    ASSERT(intr_get_status() == INTR_OFF);

    while(ioqueue_full(ioq)){
        /* 若多个线程同时访问空队列 则第二个线程之后的线程则会在这里阻塞 等待第一个线程 */
        lock_acquire(&ioq->lock);
        _ioq_wait(&ioq->producer);
        lock_release(&ioq->lock);
    }
    /* 放入该字节 */
    ioq->buf[ioq->head] = ch;
    /* 指向下一个要写入的位置 */
    ioq->head = _next_pos(ioq->head);

    /* 如果写入了一个字节并且等待队列不为空的消费者不为NULL,则唤醒 */
    if(ioq->consumer != NULL){
        _ioq_wakeup(&ioq->consumer);
    }

}


void ioqueue_init(struct ioqueue* ioq){
    lock_init(&ioq->lock);
    ioq->consumer = ioq->producer = NULL;
    ioq->tail = ioq->head = 0;
}